package com.basker.pisces.domain.command.run;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;

import com.basker.pisces.context.LoginContext;
import com.basker.pisces.context.LoginContextHolder;
import com.basker.pisces.core.meta.IDataObjectMeta;
import com.basker.pisces.core.repository.IDataRepository;
import com.basker.pisces.domain.command.CommandResult;
import com.basker.pisces.domain.command.annotation.Command;
import com.basker.pisces.domain.command.exception.CommandExecuteRuntimeException;
import com.basker.pisces.domain.command.meta.ChangeStatusCommand;
import com.basker.pisces.domain.property.meta.Property;
import com.basker.pisces.domain.property.meta.StatusProperty;
import com.basker.pisces.domain.validation.validator.ICommandValidator;
import com.basker.pisces.domain.validation.validator.ValidStatusValidator;
import com.basker.pisces.domain.value.StatusValue;
import com.basker.pisces.utils.DateUtils;

/**
 * 执行状态更新
 * <p>
 *
 * <li>如果设置了合法状态，会先进行状态校验
 * <li>事务开始前会设置目标状态、执行时间和执行人
 * <li>如果执行失败，会对内存对象还原状态值，清空执行时间和执行人
 *
 * @author hangwen
 */
@Command
public class ChangeStatusCommandRunner extends TransactionalCommandRunner<ChangeStatusCommand> {

    private static final String OLD_STATUSES_KEY = "$OldStatuses";

    @Autowired
    private IDataRepository repository;

    @Override
    protected List<ICommandValidator> getInnerValidators() {
        List<ICommandValidator> validators = super.getInnerValidators();

        List<Integer> validStatuses = this.getCommand().getValidStatuses();
        if (!CollectionUtils.isEmpty(validStatuses)) {
            validators = new ArrayList<>(validators);
            validators.add(new ValidStatusValidator(this.getCommand().getStatusPropertyAlias(), validStatuses, null));
        }

        return validators;
    }

    @Override
    protected void onAfterThrowing(Object[] dataObjects, CommandExecuteRuntimeException exception) {
        @SuppressWarnings("unchecked")
        List<Integer> oldStatusList = (List<Integer>) this.getContext().getParameter(OLD_STATUSES_KEY);
        if (oldStatusList == null) {
            // 说明没有执行过onBeforeTransactionalInvoke
            return;
        }

        StatusProperty statusProperty = this.getStatusProperty();
        Property<?> executeTimeProperty = this.tryGetExecuteTimeProperty();
        Property<?> executeUserProperty = this.tryGetExecuteUserProperty();

        int index = 0;

        for (Object dataObject : dataObjects) {
            Object oldStatus = oldStatusList.get(index++);

            // 还原状态值
            statusProperty.setValue(dataObject, oldStatus);

            // 清除执行时间
            if (executeTimeProperty != null) {
                executeTimeProperty.setValue(dataObject, null);
            }

            // 清除执行人
            if (executeUserProperty != null) {
                executeUserProperty.setValue(dataObject, null);
            }
        }

        this.onStatusRestored(dataObjects);
    }

    @Override
    protected void onBeforeTransactionalInvoke(Object[] dataObjects) {
        List<Integer> oldStatusList = new ArrayList<>(dataObjects.length);
        this.getContext().addParameter(OLD_STATUSES_KEY, oldStatusList);

        StatusProperty statusProperty = this.getStatusProperty();
        Property<?> executeTimeProperty = this.tryGetExecuteTimeProperty();
        Property<?> executeUserProperty = this.tryGetExecuteUserProperty();

        Integer targetStatus = this.getCommand().getTargetStatus();
        Date now = DateUtils.now();
        Object userId = this.getExecuteUserId();

        for (Object dataObject : dataObjects) {
            // 存储老数据，如果出错，可以还原
            StatusValue statusValue = statusProperty.getValue(dataObject);
            oldStatusList.add((Integer) statusValue.getDataValue());

            // 更新状态值
            statusProperty.setValue(dataObject, targetStatus);

            // 设置执行时间
            if (executeTimeProperty != null) {
                executeTimeProperty.setValue(dataObject, now);
            }

            // 设置执行人
            if (executeUserProperty != null && userId != null) {
                executeUserProperty.setValue(dataObject, userId);
            }
        }

        this.onStatusPrepared(dataObjects);
    }

    /**
     * 数据状态已经准备好，开始事务前调用
     *
     * @param dataObjects
     */
    protected void onStatusPrepared(Object[] dataObjects) {

    }

    /**
     * 执行失败，数据状态已还原
     *
     * @param dataObjects
     */
    protected void onStatusRestored(Object[] dataObjects) {

    }

    @Override
    protected CommandResult transactionalInvoke(Object[] dataObjects) {
        IDataObjectMeta objectMeta = this.getContext().getEntity().getDataObjectMeta();
        Object[] idArray = this.repository.saveBatch(objectMeta, dataObjects);

        return CommandResult.ok(Arrays.asList(idArray));
    }

    /**
     * 获取当前的执行人,默认从{@link LoginContext#getUserId()}获取
     *
     * @return
     */
    private Object getExecuteUserId() {
        LoginContext loginContext = LoginContextHolder.getLoginContext();
        return loginContext != null ? loginContext.getUserId() : null;
    }

    private StatusProperty getStatusProperty() {
        return (StatusProperty) this.getContext().getEntity()
                .getPropertyByAlias(this.getCommand().getStatusPropertyAlias(), true);
    }

    private Property<?> tryGetExecuteTimeProperty() {
        String executeTimePropertyAlias = this.getCommand().getExecuteTimePropertyAlias();
        if (executeTimePropertyAlias == null) {
            return null;
        }

        return this.getEntity().getProperty(executeTimePropertyAlias, true);
    }

    private Property<?> tryGetExecuteUserProperty() {
        String executeUserPropertyAlias = this.getCommand().getExecuteUserPropertyAlias();
        if (executeUserPropertyAlias == null) {
            return null;
        }

        return this.getEntity().getProperty(executeUserPropertyAlias, true);
    }

}
